#include "headers.hxx"

#pragma MARK_DATA(__FILE__)
#pragma MARK_CODE(__FILE__)
#pragma MARK_CONST(__FILE__)

#include "tempfile.hxx"
#include <wincrypt.h>       // Used to generate random filename

//+====================================================================================
//
// Method: GetTempFilename
//
// Synopsis: Makes up a (path + 8.3) unique filename for use in directory pchPathName.
//           Uses specified prefix and extension for filename.
//           Ensures that file with generated filename doesn't yet exist. 
//           Does not actually create the file, does not test for permissions,
//           Does not verify presence of temp directory itself
//           Does not check for sufficient space on the disk.
//           Returns FALSE if unable to generate unique filename.
// Sideeffect:
//           If _pTempFileList has a reference to active file list object,
//           registers the new name with it for later deletion (for
//           temp files used when print or printpreview)
//------------------------------------------------------------------------------------
BOOL CDoc::GetTempFilename(
        const TCHAR *pchPrefixString,       //needed prefix (4 chars max)
        const TCHAR *pchExtensionString,    //needed extension (3 chars max)
              TCHAR *pchTempFileName)       //should point to TCHAR[MAX_PATH], allocated by caller
{
    BOOL  fRet = FALSE;
    const DWORD nTempPathLength = MAX_PATH - 13;  // temp path size == (MAX_PATH - ([8].[3] + '\0'))
    TCHAR szTempPath[nTempPathLength];
    TCHAR szFullName[MAX_PATH];
    HCRYPTPROV hProv = NULL;

    //verify that combined name will not be too long
    Assert(pchPrefixString && pchExtensionString);
    //verify that we are able to produce 8.3 filename
    Assert((_tcslen(pchPrefixString) <= 4) && (_tcslen(pchExtensionString) <= 3));

    // Verify input args to avoid buffer overrun.
    if (    !pchTempFileName
        ||  _tcslen(pchPrefixString) > 4
        ||  _tcslen(pchExtensionString) > 3)
        goto Cleanup;

    //set the return string to be empty
    *pchTempFileName = 0;

    // Get temp directory path
    {
        DWORD nTempPathLengthWritten = GetTempPath(nTempPathLength, szTempPath);

        // GetTempPath has two error conditions:
        // 1) If it fails (for some reason):    nTempPathLengthWritten is zero;
        // 2) If provided buffer is too small:  nTempPathLengthWritten > nTempPathLength;
        if (    nTempPathLengthWritten == 0
            ||  nTempPathLengthWritten > nTempPathLength) 
            goto Cleanup;
    }

    //verify that directory name ends with slash
    Assert(szTempPath[_tcslen(szTempPath)-1] == '\\');

    if (!CryptAcquireContextA(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
    {
#ifdef DEBUG    
        DWORD dwErr;
        dwErr = GetLastError();
#endif
        hProv = NULL;
        goto Cleanup;
    }

    // Attempt to find an unused random file name 100 times.
    for(int nAttempts = 100; nAttempts; nAttempts--)
    {
        // Need 8 alphanumeric characters.
        BYTE abRand[8];
        if (!CryptGenRandom(hProv, sizeof(abRand), abRand))
            goto Cleanup;

        //generate 'random' part of filename
        TCHAR   szRnd[9];
        int     iLen = 8 - _tcslen(pchPrefixString); //the rest of filename except prefix

        for(int i=0;i<iLen;i++)
        {
            BYTE bTemp = abRand[i] % 36;

            if (bTemp < 26)
                szRnd[i] = 'A' + bTemp;
            else
                szRnd[i] = '0' + bTemp - 26;
        }
        szRnd[iLen] = 0;

        _tcscpy(szFullName, szTempPath);
        _tcscat(szFullName, pchPrefixString);
        _tcscat(szFullName, szRnd);
        _tcscat(szFullName, _T("."));
        _tcscat(szFullName, pchExtensionString);

        //check if file doesn't exist.
        if(0xFFFFFFFF == GetFileAttributes(szFullName))
        {

            //good, it doesn't exist. Copy result.
            _tcscpy(pchTempFileName, szFullName);

            //if we are tracking, add the filename into the list
            if (    TLS(pTempFileList)
                &&  TLS(pTempFileList)->IsTracking())
            {
                TLS(pTempFileList)->AddFilename(pchTempFileName);
            }

            fRet = TRUE;
            break;
        }

    }

Cleanup:
    if (hProv)
        CryptReleaseContext(hProv, 0);

    return fRet;
}

//+====================================================================================
//
// Method: SetTempFileTracking
//
// Synopsis: Turns on/off tempfile tracking. When Tracking is on, all filenames
//           generated by CDoc::GetTempFilename will be stored in a list 
//           and files will be deleted when CDoc is passivated or 
//           CDoc::DeleteTempFiles() will be called.
//------------------------------------------------------------------------------------
BOOL CDoc::SetTempFileTracking(BOOL fTrack)
{
    if(fTrack)
    {
        if (!TLS(pTempFileList))
            TLS(pTempFileList) = new CTempFileList;

        if (!TLS(pTempFileList))
            return FALSE;

        TLS(pTempFileList)->SetTracking(fTrack);
        return TLS(pTempFileList)->IsTracking();
    }
    else
    {
        if TLS(pTempFileList)
        {
            delete TLS(pTempFileList);
            TLS(pTempFileList) = NULL;
        }

        return FALSE;
    }
}

//+====================================================================================
//
// Method: TransferTempFileList
//
// Synopsis: Packages all accumulated filenames into SAFEARRAY and
//           removes them from the list.
//------------------------------------------------------------------------------------
BOOL CDoc::TransferTempFileList(VARIANT *pvarList)
{
    if(TLS(pTempFileList))
       return TLS(pTempFileList)->TransferTempFileList(pvarList);
    else
    {
        VariantInit(pvarList);
        return TRUE;
    }
}

//+====================================================================================
//
// Implementation of CTempFileName list.
//
//------------------------------------------------------------------------------------

struct CTempFileName
{   
    TCHAR _achFileName[MAX_PATH];
    CTempFileName *_pNext;
};

CTempFileList::CTempFileList()
{
    _pHead = NULL;
    _fRememberingFiles = FALSE;
}

CTempFileList::~CTempFileList()
{
    while(_pHead)
    {
        CTempFileName *pTemp = _pHead->_pNext;
        delete _pHead;
        _pHead = pTemp;
    }
}

void CTempFileList::SetTracking(BOOL doTrack)
{
    _fRememberingFiles = doTrack;
}


BOOL CTempFileList::IsTracking()
{
    return _fRememberingFiles;
}
    

BOOL CTempFileList::AddFilename(TCHAR *pchFilename)
{
    if(IsTracking())
    {
        CTempFileName *pTemp = new CTempFileName;

        if (!pTemp)
            return FALSE;

        _tcsncpy(pTemp->_achFileName, pchFilename, MAX_PATH);
        // Just in case. _tcsncpy doesn't add zero if destination is smaller then source
        pTemp->_achFileName[MAX_PATH-1] = 0;

        //put into the list
        pTemp->_pNext = _pHead;
        _pHead = pTemp;

        return TRUE;
    }

    return FALSE;
}

BOOL CTempFileList::TransferTempFileList(VARIANT *pvarList)
{
    SAFEARRAYBOUND sabound;
    SAFEARRAY *    psa = NULL;
    LONG           cnt;
    BSTR           bstrName;
    
    if(!pvarList) return FALSE;

    VariantInit(pvarList);

    //count the length
    cnt = 0;
    for(CTempFileName *pTemp = _pHead; pTemp; pTemp = pTemp->_pNext) 
        cnt++;

    //if we don't have files, leave retval in VT_EMPTY state
    if(cnt > 0)
    {
        //allocate SAFEARRAY
        sabound.cElements = cnt;
        sabound.lLbound = 0;
        psa = SafeArrayCreate(VT_BSTR, 1, &sabound);

        cnt = 0;
        while(_pHead)
        {
            CTempFileName *pTemp = _pHead->_pNext;

            bstrName = SysAllocString(_pHead->_achFileName);
            if( !bstrName ) goto Error;

            if(S_OK != SafeArrayPutElement(psa, &cnt, bstrName)) goto Error;
           
            cnt++;

            delete _pHead;
            _pHead = pTemp;
        }


        V_ARRAY(pvarList) = psa;
        V_VT(pvarList) = VT_ARRAY | VT_BSTR;
    }

    return TRUE;
    
Error:
    if(psa) SafeArrayDestroy(psa);
    return FALSE;
}


